Skip to content

Redesign channel list, swipe actions and actions bottom sheet#6181

Open
aleksandar-apostolov wants to merge 65 commits intov7from
redesign/channel-list
Open

Redesign channel list, swipe actions and actions bottom sheet#6181
aleksandar-apostolov wants to merge 65 commits intov7from
redesign/channel-list

Conversation

@aleksandar-apostolov
Copy link
Contributor

@aleksandar-apostolov aleksandar-apostolov commented Feb 23, 2026

Goal

Redesign the channel list screen to match the updated Figma spec and add swipe-to-reveal actions. Covers layout, tokens, typography, icons, interaction states, bottom sheet redesign, and swipe gesture support.

Implementation

1. Channel List Item Layout

  • Restructured layout: avatar | name + mute + timestamp + badge | trailing padding
  • Message preview row: delivery status icon + preview text (or typing indicator)
  • Configurable mute icon position via MuteIndicatorPosition enum (INLINE_TITLE / TRAILING_BOTTOM)
  • Attachment type icons (photo, video, file, link, voice, poll, location) replace emoji in previews
  • Deleted message preview support with proper sender attribution
  • DM outgoing messages no longer show "You:" prefix (delivery icon suffices)
  • DM deleted outgoing still shows "You: Message deleted" explicitly
  • Typing indicator: DM shows "Typing", group shows proper name-based formats (1, 2, many)

2. Interaction States

  • Focus state: blue rounded border (borderCorePrimary, 2dp, radiusLg)
  • Selected state: tinted background (backgroundCoreSelected)
  • isSelected property on ChannelItemState, wired from ChannelListViewModel.selectedChannel
  • 4dp card inset so rounded shape has visible margins from list edges

3. Icons & Design System

  • New action drawables in ui-common: stream_ic_action_{archive,delete,leave,more,mute,pin,unarchive,unmute,unpin,view_info}
  • Updated Compose icons: ic_add, ic_clock, ic_error, ic_more_options, ic_muted, message_seen, message_sent

4. ChannelAction Refactor

  • ChannelAction is now a self-describing interface with icon, label, confirmationPopup
  • Each action carries its own metadata for rendering in swipe actions, bottom sheets, and dialogs
  • Removed ChannelOptionState — actions are now the single source of truth
  • Added ConfirmationPopup for destructive action confirmation

5. Channel Options Bottom Sheet

  • Redesigned header with group avatar and channel name
  • Token-based dimensions via StreamTokens
  • Removed dividers between action items
  • Aligned padding and sizing with Figma List Item spec
  • Leave action moved to end as destructive

6. Swipe-to-Reveal Actions

  • SwipeableChannelItem wraps channel items with AnchoredDraggableState-based swipe gesture
  • SwipeRevealCoordinator ensures only one item is open at a time
  • Default actions:
    • DM: More (gray, left) + Archive/Unarchive (blue, right)
    • Group: More (gray, left) + Mute/Unmute (blue, right)
  • Fallback priority: DM = Archive → Mute → Pin, Group = Mute → Archive → Pin
  • "More" opens the channel options bottom sheet (same as long-press)
  • Slot-based SwipeActionStyle enum: Primary / Secondary / Destructive
  • Kill switch: ChannelListConfig(swipeActionsEnabled = false)

Customization via ChatComponentFactory:

  1. ChannelSwipeActions() — replace which actions appear (keeps swipe mechanism)
  2. ChannelListItemContent() — full control over swipe wrapper + item

New files: SwipeableChannelItem.kt, SwipeRevealCoordinator.kt, SwipeRevealValue.kt, SwipeActionItem.kt, SwipeActionStyle.kt, DefaultChannelSwipeActions.kt

7. Supporting Components

  • Updated UnreadCountIndicator: removed white border, switched to numericMedium (10sp)
  • Updated Timestamp, delivery status tokens
  • Updated SearchResultItem and SelectedChannelMenu tokens
  • Channel list header: swapped pencil icon for plus icon, updated paddings
  • Search input: decoupled from InputField, updated radius and icon
  • Added size16 design token and channel-list typing string resources

Testing

  • All Paparazzi snapshots re-recorded
  • New snapshot: muted_channel_trailing_bottom
  • On-device tested across DM and group conversations, light and dark theme
  • Verified focus/selected states, typing indicators, attachment previews, mute positioning
  • Verified swipe gestures, single-open coordination, action execution, auto-close
  • Verified fallback priority when capabilities are restricted
  • Sample app's CustomChatComponentFactory updated to include swipe wrapper

Summary by CodeRabbit

Release Notes

  • New Features

    • Added swipe-to-reveal actions for channel items (archive, mute, pin, delete)
    • Introduced configurable mute indicator positioning and swipe action controls
    • Added channel pinning and selection visual indicators
    • Enhanced empty state messaging and skeleton loading animation
  • UI/Design Improvements

    • Redesigned channel list layout with improved spacing and typography
    • Refined message preview formatting with better sender attribution
    • Enhanced channel action menu with clearer visual hierarchy
    • Improved unread count badge styling and colors
  • Bug Fixes

    • Fixed message deletion indicators and error state visibility
    • Improved read status icon display

- ChannelsScreen: search section padding top 8dp→16dp, horizontal 12dp→16dp (spacingMd), bottom 8dp (spacingXs)
- SearchInput DefaultSearchLeadingIcon: 16x16dp size, leading 16dp padding, tint textLowEmphasis→textTertiary
- SearchInput DefaultSearchLabel: typography body→bodyDefault, color textLowEmphasis→textTertiary
- SearchInput: add 48dp minHeight, innerPadding start=spacingXs/end=spacingMd/v=spacingSm
- InputField: border color borders→borderCoreDefault
- Container: padding 8dp→spacingSm (12dp), add spacedBy(spacingXs) gap between children
- Add 1dp bottom border (borderCoreDefault) replacing drop shadow
- Elevation default: headerElevation (4dp)→0.dp
- Title typography: title3Bold→headingSmall, color: textHighEmphasis→textPrimary
- Title padding: 16.dp→spacingMd (token-aligned)
- Leading avatar: 40dp visual wrapped in 48dp hit target Box
- Trailing button: accentPrimary bg, CircleShape, 0dp elevation, 48dp hit target
- Leading: avatar 40dp→channelListItemAvatarSize (48dp), all padding spacingMd (16dp)
- Center content major restructure: timestamp+badge moved from trailing→title row, delivery status moved→message row
- Channel name: bodyBold+fontSize16→bodyDefault, textHighEmphasis→textPrimary
- Mute icon: moved inline into title row, textLowEmphasis→textTertiary
- Message preview: body→captionDefault, textLowEmphasis→textSecondary
- Typing indicator: gap 6dp→spacing2xs, body→captionDefault, textLowEmphasis→textSecondary
- Trailing content: replaced with empty Spacer stub (spacingMd=16dp) for API compatibility
…tatus

- UnreadCountIndicator: errorAccent (red)→accentPrimary (blue), add 2dp presenceBorder border, min 18dp→20dp, RoundedCornerShape(9)→pill (50), captionBold→numericExtraLarge, Color.White→badgeTextOnAccent
- Timestamp: default style footnote→captionDefault (14sp), textLowEmphasis→textTertiary, add lineHeightNormal (20sp)
- StreamDimens: add channelListItemAvatarSize=48.dp field (channelAvatarSize=40.dp retained for header)
- SearchResultItem leading: avatar 40dp→48dp, padding channelItemHorizontalPadding→spacingMd (16dp)
- SearchResultItem center: bodyBold+fontSize16→bodyDefault, textHighEmphasis→textPrimary, body→captionDefault, textLowEmphasis→textSecondary, add 4dp gap
- SearchResultItem trailing: padding updated to spacingMd, timestamp footnote→captionDefault+textTertiary+20sp lineHeight, alignment Bottom→CenterVertically
- SearchResultItem container Row: add Arrangement.spacedBy(spacingMd) for 16dp avatar-content gap
- SelectedChannelMenu header: title3Bold→headingSmall, textHighEmphasis→textPrimary, footnoteBold→captionDefault, textLowEmphasis→textSecondary
- ChannelOptions: all non-destructive options textHighEmphasis→textPrimary, textLowEmphasis→textSecondary (delete option unchanged)
- ChatComponentFactory ChannelOptionsItem: bodyBold→bodyDefault
- StreamHorizontalDivider: borders→borderCoreSurfaceSubtle
The Figma design uses a plus icon for the new channel button,
not the pencil/edit icon. Switched from stream_compose_ic_new_chat
to stream_compose_ic_add with explicit 24dp sizing.
Move SearchInput to use BasicTextField directly, matching the
pattern used by the new message composer. This removes the
dependency on InputField and its composer-specific theme
(inputBackground), making the search bar transparent so it
inherits the parent background color. Works correctly in both
light and dark mode.
Use radius3xl (24dp) matching the composer's MessageInputShape
instead of radiusLg (12dp). Move padding into decorationBox so
the search icon isn't clipped by the border/clip modifiers.
Match the composer's icon size (icon/size/md = 20dp) for
consistent icon sizing across the SDK.
Replace the filled 24dp search icon with the stroked 20dp
variant from Figma (IconMagnifyingGlassSearch), matching the
new design system's stroked icon style.
Add Box with 10dp padding and center alignment inside the Surface,
matching Figma button/padding-x/icon-only/md and button/padding-y/md
tokens. The icon was not centered and appeared oversized.
…l list preview

Add inline preview icons for photo, video, file, link, and location
attachment types in the channel list message preview. Handle deleted
messages, error state indicator, and empty chat placeholder per Figma
spec. Voice messages and location now include sender name prefix for
group chats.
The getPreviewMessage function filters out deleted messages, so the
formatter's isDeleted check was never reached. Add
getLastMessageIncludingDeleted to pass deleted messages to the formatter.
Suppress delivery status icon for deleted messages.
Add Modifier.fillMaxSize() to all inline preview icons so they
properly scale to the 16sp placeholder regardless of their intrinsic
drawable size (some are 12dp, mic is 20dp, poll is 24dp).
…illMaxSize

Revert fillMaxSize() on icons — the real issue is PlaceholderVerticalAlign.Center
centers in the line box (16sp) rather than the text glyphs (14sp). TextCenter
fixes the vertical alignment against the caption text.
…lignment

- getSenderDisplayName now returns the sender's name for group chats
  (was only returning "You" for current user, null for everyone else)
- Add isDirectMessaging parameter to formatMessagePreview so the formatter
  knows when to suppress the sender name (DMs only)
- ChannelItem passes isOneToOne to the formatter
- Reduce icon placeholder from 16sp to 14sp (matching font size) with
  Modifier.size(14.dp) to prevent text baseline displacement
- Configurable mute icon position (inline title / trailing bottom)
- DM typing shows "Typing" without name; group shows proper name formats
- Remove "You:" prefix from DM outgoing non-deleted messages
- Replace hardcoded mute icon size with design token
# Conflicts:
#	stream-chat-android-compose/api/stream-chat-android-compose.api
…review

Replace hardcoded 📊 emoji with ic_poll drawable via appendInlineContent,
matching the pattern used by all other attachment type previews.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.69 MB 0.44 MB 🟡
stream-chat-android-ui-components 10.60 MB 10.97 MB 0.37 MB 🟡
stream-chat-android-compose 12.81 MB 11.95 MB -0.86 MB 🚀

@aleksandar-apostolov aleksandar-apostolov changed the title feat(compose): channel list visual redesign — Figma alignment Channel list redesign Feb 23, 2026
Fix detekt MaxLineLength/LongMethod/MagicNumber violations, spotless
formatting, and docs compile error from formatMessagePreview signature.
@aleksandar-apostolov aleksandar-apostolov changed the title Channel list redesign Apply new Channel list design Feb 24, 2026
…d poll sender prefix and chatTextRead token

Replace filled delivery status icons (clock, single check, double check)
with stroked outlines per Figma spec. Add sender name prefix for poll
messages in group chats. Add chatTextRead semantic color token for read
status icon tint.
Badge number felt oversized inside the 20dp pill with 2dp border.
Switched from numericExtraLarge (14sp) to numericLarge (12sp) for
better visual proportion.
Add visual feedback for focus (blue rounded border) and selected
(tinted background) states on channel list items, matching the Figma
design system interaction states.
Icon: spacingXl (24dp) with spacingSm (12dp) horizontal padding.
Replaces hardcoded 48dp/12dp values with design system tokens.
No dividers between channel options per Figma design.
Row height 48dp → 44dp, horizontal padding spacing2xs (4dp), icon 24dp
with 8dp end gap. Matches Mobile / List Item Figma component.
…ngMd

spacing2xs (4dp) → spacingMd (16dp) to match visible Figma padding.
spacingXs (8dp) bottom content padding for breathing room below last
action item.
@aleksandar-apostolov aleksandar-apostolov changed the title Apply new Channel list design feat(compose): redesign channel list with swipe actions Feb 25, 2026
@aleksandar-apostolov aleksandar-apostolov changed the title feat(compose): redesign channel list with swipe actions Redesign channel list, swipe actions and actions bottom sheet Feb 25, 2026
# Conflicts:
#	stream-chat-android-compose/api/stream-chat-android-compose.api
#	stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatConfig.kt
- Replace spinner with shimmer skeleton items (8 placeholder rows
  matching channel item layout: avatar + title/timestamp + message)
- Redesign empty state: 32dp icon, "No conversations yet" copy,
  optional "Start a chat" CTA wired to header action
- Update ChatComponentFactory.ChannelListEmptyContent with
  onStartChatClick callback
- ChannelsScreen passes onHeaderActionClick as empty state CTA
@aleksandar-apostolov aleksandar-apostolov marked this pull request as ready for review February 25, 2026 15:59
@aleksandar-apostolov aleksandar-apostolov requested a review from a team as a code owner February 25, 2026 15:59
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

This pull request introduces a comprehensive redesign of the Stream Chat Android channel list UI, refactors the channel action system from a sealed class to an interface-based model with richer metadata, implements swipe-to-reveal actions for channel items, and updates message preview formatting. It includes planning documentation, configuration updates, and supporting visual assets.

Changes

Cohort / File(s) Summary
Planning & Documentation
.planning/PROJECT.md, .planning/REQUIREMENTS.md, .planning/ROADMAP.md, .planning/STATE.md, .planning/config.json, redesign/channel-list/GAPS.md
New planning documentation for release process migration, project roadmap phases, requirements traceability, and design gap analysis for channel list redesign.
Channel List State & Configuration
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ItemState.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatConfig.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/StreamTokens.kt
Added isPinned and isSelected properties to ChannelItemState; introduced MuteIndicatorPosition enum and ChannelListConfig data class with swipe actions and mute indicator position settings; added size16 token.
Channel List UI Redesign
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelList.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/header/ChannelListHeader.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt
Major visual overhaul: elevated avatar sizing, refactored center content with inline title row and mute indicator placement options, enhanced message preview row with draft/typing indicators, updated header layout with divider, improved spacing using StreamTokens, simplified trailing content, added selection/focus states.
Swipe Actions Infrastructure
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealCoordinator.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealValue.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeableChannelItem.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionItem.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionStyle.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/DefaultChannelSwipeActions.kt
New swipe-to-reveal system with coordinator for managing single-open item state, anchored draggable states, default swipe actions (Mute/Unmute/Pin/Unpin/Archive/Unarchive/More), and three action styles (Primary/Secondary/Destructive).
Channel Menu & Actions UI
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/info/SelectedChannelMenu.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
Replaced isMuted and ChannelOptionState with channelActions list; introduced buildDefaultChannelActions helper; updated menu signature to accept actions; wired confirmation flow via executeOrConfirm with ConfirmationPopup; added empty state content customization.
Channel Action System Refactoring
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt, stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ConfirmationPopup.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/actions/internal/ChannelOptionItemsFactory.kt
Converted ChannelAction from sealed class to public interface with extended metadata (icon, label, onAction callback, requiredCapability, confirmationPopup, isDestructive); introduced ConfirmationPopup data class; updated all action implementations (ViewInfo, LeaveGroup, DeleteConversation, Pin\/Archive\, Mute\*) with explicit UI/behavior fields; refactored factories to return actions instead of option items.
Removed/Deleted Types
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ChannelOptionState.kt, stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/actions/internal/ChannelOptionItem.kt
Deleted ChannelOptionState and ChannelOptionItem classes; replaced by action-based system.
Message Preview & Formatting
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessageUtils.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/ChannelUtils.kt
Added isDirectMessaging parameter to message preview and sender name formatting; enhanced preview types (system messages, polls, audio, location, attachments) with typed attachment rendering; introduced getLastMessageIncludingDeleted utility; updated sender name logic for DM contexts.
ViewModel & Lifecycle Updates
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt
Replaced performChannelAction with executeOrConfirm for conditional confirmation flow; added confirmPendingAction for post-confirmation execution; populated isPinned in ChannelItemState.
Component Factory & Theme
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/Timestamp.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/StreamHorizontalDivider.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt, stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.kt
Refactored SearchInput to BasicTextField with internal state sync; updated Timestamp styling (captionDefault + StreamTokens); changed divider color to borderCoreSubtle; added IsErrorIcon to sync failure feedback; updated badge colors and shapes (Primary, pill-shaped).
Sample App & Documentation
stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt, stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt, stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt, stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/channels/SelectedChannelMenu.kt, stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/compose/general/ChatTheme.kt
Updated sample activity to use buildDefaultChannelActions and executeOrConfirm; added conditional swipe wrapping in CustomChatComponentFactory; refactored documentation examples to match new channel actions API; updated E2E test assertions for message preview sender logic.
Visual Assets
stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_*.xml, stream-chat-android-ui-common/src/main/res/drawable/stream_ic_action_*.xml, stream-chat-android-compose/src/main/res/values/strings.xml
New/updated drawable icons for add, clock, error, search, message status, more options, muted state; new action icons (archive, delete, leave, more, mute, pin, unarchive, unmute, unpin, view info); expanded string resources for typing, swipe actions, empty states, and message previews (audio, photo, video, file, link, location, polls).
API & Manifest
stream-chat-android-compose/api/stream-chat-android-compose.api, stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
API surface expansions documenting public additions: new state properties (isPinned, isSelected, endOfChannels, loading, loadingMore), swipe coordinator/value types, ChannelListConfig enum/properties, new composables (DefaultChannelItemDivider, SwipeableChannelItem, ChannelSwipeActions), factory method updates, and ChannelAction interface refactoring.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Channel UI
    participant Coordinator as SwipeRevealCoordinator
    participant ViewModel as ChannelListViewModel
    participant Action as ChannelAction

    User->>UI: Swipes channel item right
    UI->>Coordinator: onItemOpened(channelCid)
    Coordinator->>Coordinator: Close all other open items
    UI->>UI: Show swipe actions (Mute/Pin/Archive/More)
    
    User->>UI: Taps swipe action
    UI->>ViewModel: executeOrConfirm(action)
    
    alt Action requires confirmation
        ViewModel->>UI: Show ConfirmationPopup
        User->>UI: Confirms action
        UI->>ViewModel: confirmPendingAction()
    else No confirmation needed
        ViewModel->>ViewModel: Execute immediately
    end
    
    ViewModel->>Action: action.onAction()
    Action->>Action: Perform channel operation
    ViewModel->>UI: Update channel state
    UI->>Coordinator: closeAll()
    UI->>UI: Hide swipe actions, refresh list
Loading
sequenceDiagram
    participant Compose as Compose Layout
    participant ChannelItem as ChannelItem
    participant Config as ChannelListConfig
    participant Theme as ChatTheme

    Compose->>Config: Query muteIndicatorPosition
    Config-->>Compose: Returns INLINE_TITLE or TRAILING_BOTTOM
    
    alt INLINE_TITLE
        Compose->>ChannelItem: Render mute icon in title row
        ChannelItem->>ChannelItem: Center content layout
    else TRAILING_BOTTOM
        Compose->>ChannelItem: Render mute icon in message preview row
        ChannelItem->>ChannelItem: Trailing bottom layout
    end
    
    Compose->>Config: Query swipeActionsEnabled
    Config-->>Compose: Returns true/false
    
    alt swipeActionsEnabled true
        Compose->>ChannelItem: Wrap with SwipeableChannelItem
        ChannelItem->>Theme: Get DefaultChannelSwipeActions
    else swipeActionsEnabled false
        Compose->>ChannelItem: Render standard item
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 The channel list hops with new swipes so fine,
Actions dance freely on configurable spine,
Pinned states and selections bring clarity bright,
While mute icons find their perfect light—
From sealed to interface, the redesign takes flight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main changes: channel list redesign, swipe actions, and bottom sheet redesign.
Description check ✅ Passed The PR description comprehensively covers Goal, Implementation, and Testing sections with detailed subsections for each major change area.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign/channel-list

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt (1)

74-92: ⚠️ Potential issue | 🟡 Minor

KDoc on the public overload does not document the newly handled FAILED_PERMANENTLY state.

The @param block and description make no mention of when/how the error state is rendered. Consider adding a note so consumers understand the full state machine.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt`
around lines 74 - 92, Update the KDoc for the public MessageReadStatusIcon
composable to document the newly-handled FAILED_PERMANENTLY state: mention that
the composable now renders an error/failed state when message.syncStatus ==
SyncStatus.FAILED_PERMANENTLY (or equivalent), describe which icon/composable
(e.g., isPendingIcon/isSentIcon/isDeliveredIcon or a new failure representation)
will be shown in that case, and add an `@param` entry or sentence explaining when
and why the failed state is displayed so callers understand the full state
machine.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt (1)

97-101: ⚠️ Potential issue | 🟡 Minor

Use local text state for clear/placeholder visibility.

The clear button and placeholder are keyed off query, but typing first updates textState. This can cause transient flicker/desync until parent state catches up.

💡 Proposed fix
-    val trailingContent: (`@Composable` RowScope.() -> Unit)? = if (isFocused && query.isNotEmpty()) {
+    val hasText = textState.text.isNotEmpty()
+    val trailingContent: (`@Composable` RowScope.() -> Unit)? = if (isFocused && hasText) {
         clearButton
     } else {
         null
     }
@@
-                    if (query.isEmpty()) {
+                    if (textState.text.isEmpty()) {
                         label()
                     }

Also applies to: 163-164

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt`
around lines 97 - 101, The clear button and placeholder visibility are currently
driven by the external `query` prop which lags the local input buffer, causing
flicker; change the visibility checks to use the local compose state
(`textState`) instead—e.g., in the `trailingContent` assignment (where
`isFocused && query.isNotEmpty()` is used) and the placeholder visibility logic
(lines referenced around 163-164) use `isFocused && textState.text.isNotEmpty()`
or `textState.value.text.isNotEmpty()` depending on your TextFieldValue type so
the UI reacts immediately to local edits before the parent `query` updates.
♻️ Duplicate comments (1)
.planning/ROADMAP.md (1)

1-130: No additional issues in this file beyond the scope concern already noted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.planning/ROADMAP.md around lines 1 - 130, Remove the stray
"[duplicate_comment]" marker from the review comment content associated with
"Roadmap: Release Process Migration" so the comment reads only the intended
sentence ("No additional issues in this file beyond the scope concern already
noted.")—locate the review text containing the duplicate token and delete it,
leaving the comment clean and unambiguous.
🧹 Nitpick comments (18)
stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_search.xml (1)

24-29: strokeColor should use a placeholder color, not a semantic gray.

The project convention for stream_compose_ic_*.xml drawables is to use a hardcoded placeholder color (e.g. #FFFFFF) because tint is applied programmatically by the Compose UI layer at runtime. Using #687385 (a specific semantic gray) embeds a design-specific value in the asset instead of a neutral placeholder. While Android's src_in tint mode will still override it at render time, it violates the established pattern and can cause the icon to render with the wrong color if the composable consumer forgets to apply a tint.

🎨 Proposed fix
-        android:strokeColor="#687385"
+        android:strokeColor="#FFFFFF"

Based on learnings: "For drawable XMLs named stream_compose_ic_*.xml in the stream-chat-android-compose module, use hardcoded placeholder colors because tint is applied programmatically when used in composables to ensure theme-aware coloring at runtime."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_search.xml`
around lines 24 - 29, Replace the hardcoded semantic gray in the search drawable
with the project's placeholder color: open stream_compose_ic_search.xml and
change the android:strokeColor attribute (and any other explicit semantic colors
in this drawable) from "#687385" to the neutral placeholder (e.g. "#FFFFFF") so
the Compose layer can apply tint programmatically; ensure android:fillColor
remains transparent and strokeWidth/strokeLineCap/strokeLineJoin are unchanged.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory.kt (1)

67-132: Avoid undocumented suppression by extracting a small icon builder helper.

Line [67] introduces @Suppress("LongMethod") without rationale. Prefer removing the suppression and extracting duplication into a helper so the method stays within limits naturally.

♻️ Proposed refactor
-    `@Suppress`("LongMethod")
     override fun createPreviewIcons(): Map<String, InlineTextContent> {
         val placeholder = Placeholder(
             width = 14.sp,
             height = 14.sp,
             placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
         )
         val iconModifier = Modifier.size(14.dp)
         return mapOf(
-            VOICE_MESSAGE to InlineTextContent(placeholder) {
-                Icon(
-                    modifier = iconModifier,
-                    painter = painterResource(id = R.drawable.stream_compose_ic_mic),
-                    contentDescription = null,
-                    tint = ChatTheme.colors.textSecondary,
-                )
-            },
-            PHOTO to InlineTextContent(placeholder) {
-                Icon(
-                    modifier = iconModifier,
-                    painter = painterResource(id = R.drawable.stream_compose_ic_camera),
-                    contentDescription = null,
-                    tint = ChatTheme.colors.textSecondary,
-                )
-            },
+            previewIcon(VOICE_MESSAGE, R.drawable.stream_compose_ic_mic, placeholder, iconModifier),
+            previewIcon(PHOTO, R.drawable.stream_compose_ic_camera, placeholder, iconModifier),
             // ...same for the rest
         )
     }
+
+    private fun previewIcon(
+        key: String,
+        drawableRes: Int,
+        placeholder: Placeholder,
+        iconModifier: Modifier,
+    ): Pair<String, InlineTextContent> =
+        key to InlineTextContent(placeholder) {
+            Icon(
+                modifier = iconModifier,
+                painter = painterResource(id = drawableRes),
+                contentDescription = null,
+                tint = ChatTheme.colors.textSecondary,
+            )
+        }

As per coding guidelines: "**/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory.kt`
around lines 67 - 132, Remove the undocumented `@Suppress`("LongMethod") on
MessagePreviewIconFactory.createPreviewIcons and refactor duplicated
InlineTextContent + Icon creation into a small private helper (e.g.,
buildPreviewIcon(drawableRes: Int): InlineTextContent) that captures the
Placeholder and Icon modifier; then replace each anonymous InlineTextContent
block (for VOICE_MESSAGE, PHOTO, VIDEO, FILE, LINK, LOCATION, POLL) with calls
to that helper, keeping Placeholder and iconModifier shared and eliminating the
long-method suppression.
.planning/STATE.md (1)

5-9: Consider adding an owner/update cadence for status freshness.

Lines 5–9 are time-sensitive and can become stale quickly; adding an “owner” and expected refresh cadence would reduce drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.planning/STATE.md around lines 5 - 9, Update the time-sensitive block by
adding explicit ownership and a refresh cadence so status remains fresh: add
fields such as "Owner: <team/person>" and "Refresh cadence: <frequency, e.g.
weekly/monthly/after-change>" alongside the existing "Phase", "Plan", "Status",
and "Last activity" entries, and include a short sentence describing who is
responsible for updates and when to update (e.g., "Owner will review and refresh
this status every X days or after significant changes").
.planning/PROJECT.md (1)

44-49: Add explicit target branch and coordination note in constraints.

Please include that changes should target develop and note release-owner coordination when touching changelog/release-process artifacts.

Based on learnings: Target develop branch for PRs; sync with release owners before touching versioning, publishing, or changelog scripts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.planning/PROJECT.md around lines 44 - 49, Update the Constraints section to
explicitly state that PRs must target the develop branch and add a coordination
note that any changes to versioning, publishing, changelog or release-process
artifacts (e.g., CHANGELOG.md, release scripts) require prior sync with the
release owner; reference the "Constraints" heading and the file name
CHANGELOG.md so reviewers can find and enforce the rule.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt (1)

83-110: isErrorIcon slot is absent from the public overload, breaking customisation parity.

All four other delivery states (isReadIcon, isPendingIcon, isSentIcon, isDeliveredIcon) are exposed as slot parameters so integrators can override them. The newly-handled FAILED_PERMANENTLY branch (line 108) hard-wires IsErrorIcon with no override path. This is inconsistent with the rest of the API surface and prevents theming/branding of the error state.

♻️ Proposed refactor
 `@Composable`
 public fun MessageReadStatusIcon(
     message: Message,
     isMessageRead: Boolean,
     modifier: Modifier = Modifier,
     isMessageDelivered: Boolean = false,
     readCount: Int = 0,
     isReadIcon: `@Composable` () -> Unit = { IsReadCount(modifier = modifier, readCount = readCount) },
     isPendingIcon: `@Composable` () -> Unit = { IsPendingIcon(modifier = modifier) },
     isSentIcon: `@Composable` () -> Unit = { IsSentIcon(modifier = modifier) },
     isDeliveredIcon: `@Composable` () -> Unit = { IsDeliveredIcon(modifier = modifier) },
+    isErrorIcon: `@Composable` () -> Unit = { IsErrorIcon(modifier = modifier) },
 ) {
     val syncStatus = message.syncStatus

     when (syncStatus) {
         SyncStatus.IN_PROGRESS,
         SyncStatus.SYNC_NEEDED,
         SyncStatus.AWAITING_ATTACHMENTS,
         -> isPendingIcon()

         SyncStatus.COMPLETED -> when {
             isMessageRead -> isReadIcon()
             isMessageDelivered -> isDeliveredIcon()
             else -> isSentIcon()
         }

-        SyncStatus.FAILED_PERMANENTLY -> IsErrorIcon(modifier = modifier)
+        SyncStatus.FAILED_PERMANENTLY -> isErrorIcon()
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt`
around lines 83 - 110, The MessageReadStatusIcon API hard-codes IsErrorIcon for
SyncStatus.FAILED_PERMANENTLY and should expose an error slot for parity; add a
new parameter isErrorIcon: `@Composable` () -> Unit to MessageReadStatusIcon
(defaulting to { IsErrorIcon(modifier = modifier) }) and call that isErrorIcon()
in the FAILED_PERMANENTLY branch instead of directly invoking IsErrorIcon, so
integrators can override the error state rendering.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.kt (1)

36-47: KDoc is missing the color parameter.

The public function signature exposes color as an overridable parameter whose default has just changed (from accentError to accentPrimary). Documenting its intent and the new default helps API consumers understand the change.

📝 Proposed addition
  * `@param` unreadCount The number of messages the user didn't read.
  * `@param` modifier Modifier for styling.
+ * `@param` color Background color of the badge. Defaults to [ChatTheme.colors.accentPrimary].
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.kt`
around lines 36 - 47, Update the KDoc for the composable function
UnreadCountIndicator to include the missing `@param` entry for color, describing
that it controls the badge tint and noting the default value
(ChatTheme.colors.accentPrimary) and its intent (to match the app's accent for
unread badges); keep other parameter docs intact and ensure the KDoc reflects
the changed default from the previous accentError to accentPrimary so callers
understand the behavioral change.
stream-chat-android-compose/src/main/res/values/strings.xml (1)

31-34: Prefer positional numeric placeholders in new plurals.

Using %1$d instead of %d makes translator-side reordering safer and keeps formatting style more localization-friendly.

♻️ Proposed i18n formatting tweak
     <plurals name="stream_compose_channel_list_typing_many">
-        <item quantity="one">%d person is typing</item>
-        <item quantity="other">%d people are typing</item>
+        <item quantity="one">%1$d person is typing</item>
+        <item quantity="other">%1$d people are typing</item>
     </plurals>
@@
     <plurals name="stream_compose_photos_preview">
-        <item quantity="one">%d Photo</item>
-        <item quantity="other">%d Photos</item>
+        <item quantity="one">%1$d Photo</item>
+        <item quantity="other">%1$d Photos</item>
     </plurals>
     <plurals name="stream_compose_videos_preview">
-        <item quantity="one">%d Video</item>
-        <item quantity="other">%d Videos</item>
+        <item quantity="one">%1$d Video</item>
+        <item quantity="other">%1$d Videos</item>
     </plurals>
     <plurals name="stream_compose_files_preview">
-        <item quantity="one">%d File</item>
-        <item quantity="other">%d Files</item>
+        <item quantity="one">%1$d File</item>
+        <item quantity="other">%1$d Files</item>
     </plurals>

Also applies to: 182-193

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@stream-chat-android-compose/src/main/res/values/strings.xml` around lines 31
- 34, The plural resource stream_compose_channel_list_typing_many uses
unpositioned numeric placeholders ("%d"); update both <item> entries to use
positional placeholders ("%1$d") so translators can reorder safely and maintain
consistent formatting; search for other plural resources (e.g., the similar
block referenced at lines 182-193) and apply the same replacement to their
numeric placeholders as well to ensure all plural strings use "%1$d".
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt (2)

93-93: Verify that spacedBy plus inner padding matches the intended Figma gap.

Arrangement.spacedBy(spacingMd) (16dp) applies between all three children. Combined with the center content's own start = spacing2xs (4dp), the effective visual gap between the avatar and text is 20dp, not 16dp. If the design intent is exactly 16dp edge-to-edge, consider removing the center content's horizontal start padding, or removing the arrangement spacing and relying solely on per-child padding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt`
at line 93, The horizontalArrangement uses
Arrangement.spacedBy(StreamTokens.spacingMd) which adds 16dp between all
children while the center content also has start = StreamTokens.spacing2xs
(4dp), producing a 20dp gap; to match the Figma 16dp edge-to-edge spacing remove
the center content's start padding (the padding on the composable that sets
start = StreamTokens.spacing2xs) or alternatively remove the
Arrangement.spacedBy and apply per-child paddings so only one 16dp gap exists
between the avatar and text; update the code references:
horizontalArrangement/Arrangement.spacedBy(StreamTokens.spacingMd) and the
center content padding (start = StreamTokens.spacing2xs) accordingly.

207-213: Remove redundant textStyle override.

The Timestamp composable's default textStyle is ChatTheme.typography.captionDefault.copy(color = ChatTheme.colors.textTertiary, lineHeight = StreamTokens.lineHeightNormal). Since StreamTokens.lineHeightNormal = 20.sp, this explicit override passes identical values and can be removed:

-        Timestamp(
-            date = searchResultItemState.message.createdAt,
-            textStyle = ChatTheme.typography.captionDefault.copy(
-                color = ChatTheme.colors.textTertiary,
-                lineHeight = 20.sp,
-            ),
-        )
+        Timestamp(date = searchResultItemState.message.createdAt)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt`
around lines 207 - 213, The explicit textStyle override on the Timestamp
composable is redundant because Timestamp already defaults to
ChatTheme.typography.captionDefault.copy(color = ChatTheme.colors.textTertiary,
lineHeight = StreamTokens.lineHeightNormal); remove the textStyle parameter from
the Timestamp call (the one using searchResultItemState.message.createdAt) so it
relies on the default style provided by Timestamp/ChatTheme.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.kt (1)

214-214: Prefer decomposition over an undocumented LongMethod suppression.

DefaultChannelItemCenterContent is now doing multiple responsibilities (title row, preview row, status/mute variants). Splitting into focused helpers would keep it maintainable without suppression.

As per coding guidelines: **/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.kt`
at line 214, The suppression of LongMethod should be removed and
DefaultChannelItemCenterContent refactored into smaller helper composables:
extract the title row rendering into a function like renderChannelTitleRow, the
preview/last-message row into renderChannelPreviewRow, and status/mute and
unread badge logic into renderChannelStatusOrMute; update
DefaultChannelItemCenterContent to compose these helpers, keep parameter lists
minimal and pass only necessary state (channel, lastMessagePreview, isMuted,
isOnline, unreadCount), and add explicit `@OptIn` annotations if any experimental
APIs are used instead of suppressing LongMethod.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt (1)

63-67: Update public API docs for isDirectMessaging and avoid undocumented suppression.

The new public parameter is missing KDoc, and the new LongMethod suppression should be avoided or explicitly justified.

📝 Proposed KDoc update
     /**
      * Generates a preview text for the given message.
      *
      * `@param` message The message whose data is used to generate the preview text.
      * `@param` currentUser The currently logged in user.
+     * `@param` isDirectMessaging Whether preview formatting should use direct-message rules.
      * `@return` The formatted text representation for the given message.
      */

As per coding guidelines: **/*.kt: Document public APIs with KDoc, including thread expectations and state notes; and avoid suppressions unless documented.

Also applies to: 163-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt`
around lines 63 - 67, Add KDoc for the public function formatMessagePreview and
its new parameter isDirectMessaging: document what the function returns,
expected thread/state assumptions, and explain the boolean’s effect (e.g., how
message preview differs in direct messaging vs channels). Remove the
undocumented `@Suppress`("LongMethod") (or if you must keep it, add a KDoc
justification comment explaining why the method legitimately exceeds length and
why splitting isn't appropriate). Ensure the KDoc appears above the
formatMessagePreview declaration and explicitly describes isDirectMessaging
behavior and any thread/state constraints.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt (1)

74-74: Avoid introducing an undocumented LongMethod suppression.

Consider extracting parts of SearchInput (state sync / decoration) into helpers instead of suppressing the rule directly.

As per coding guidelines: **/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt`
at line 74, Remove the undocumented `@Suppress`("LongMethod") on SearchInput:
instead split the large function by extracting the state synchronization logic
and the TextField decoration into small helpers (e.g., create functions named
syncSearchState(...) and decorateSearchTextField(...) called from SearchInput)
so the body is shorter and the lint rule is satisfied; if any experimental APIs
are required, replace suppressions with explicit `@OptIn`(...) annotations on
SearchInput or the new helper functions and document the reason in a brief KDoc.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionStyle.kt (1)

21-36: Add thread/state notes to enum KDoc.

Please include a brief statement about state/thread expectations for this public enum (for example, stateless style marker safe for use from any thread).

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionStyle.kt`
around lines 21 - 36, The public enum SwipeActionStyle lacks KDoc about
thread/state expectations; update the KDoc for SwipeActionStyle to add a short
thread/state note stating that this enum is a stateless, immutable style marker
and safe to reference from any thread (no synchronization required), and include
that it carries no mutable state and is safe for concurrent use in UI code such
as composables and background threads.
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ConfirmationPopup.kt (1)

19-30: Expand public KDoc with thread/state expectations.

This public API is documented, but it should also state thread expectations and state behavior (for example: immutable value object, safe to pass across threads, fields are resolved UI text).

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ConfirmationPopup.kt`
around lines 19 - 30, Update the KDoc for the public data class
ConfirmationPopup to include thread and state expectations: state that this is
an immutable value object (data class) safe to pass across threads, its
properties (title, message, confirmButtonText) are resolved UI text and should
not hold mutable or lifecycle-bound references, and note any expectations about
resolution timing (e.g., already localized/resolved before construction) so
callers know it's safe for background/worker threads and UI usage; reference the
ConfirmationPopup type and its properties when adding these notes.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/DefaultChannelSwipeActions.kt (1)

164-170: firstOrNull { it != null } is equivalent to firstNotNullOfOrNull { it } — but more importantly, for DMs the archive action is always returned.

Since archiveAction and pinAction are never null, the first non-null in the DM list [archiveAction, muteAction, pinAction] is always archiveAction, and in the group list [muteAction, archiveAction, pinAction], it's muteAction if available, otherwise archiveAction. The fallback priority works, but the function can never return null overall (pin is always non-null). Consider simplifying the return type to non-nullable if that's the intent, or use filterNotNull().first().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/DefaultChannelSwipeActions.kt`
around lines 164 - 170, The current selection logic in
DefaultChannelSwipeActions builds candidates = listOf(...) containing
archiveAction, muteAction, pinAction and then returns candidates.firstOrNull {
it != null }, but since pinAction (and archiveAction/pinAction) are non-null the
function can never return null; update the implementation to reflect that
non-null guarantee: either change the function return type to non-nullable and
return candidates.filterNotNull().first() (or .first { true } after filtering)
or, if null is genuinely possible, adjust the candidates construction so some
entries can be null and use filterNotNull().firstOrNull(); reference the
candidates variable and the archiveAction/muteAction/pinAction symbols when
making the change.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt (1)

95-95: Document or remove the blanket suppression on buildDefaultChannelActions.

Line 95 adds @Suppress("LongMethod", "LongParameterList") without rationale. Prefer further decomposition (or a scoped justification comment) instead of an undocumented suppression.

As per coding guidelines: **/*.kt: "Use @OptIn annotations explicitly; avoid suppressions unless documented".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt`
at line 95, The suppression on buildDefaultChannelActions is too broad and
undocumented; remove the blanket `@Suppress`("LongMethod", "LongParameterList")
and either refactor buildDefaultChannelActions into smaller helper functions
(e.g., extract action creation into createChannelActionList, buildActionItem,
configureActionHandlers) to reduce method length and parameter count, or if
suppression is unavoidable, replace it with a narrow, documented suppression
scoped to the specific offending expression(s) and add a one-line rationale
comment above it; also ensure any experimental APIs used are annotated with
`@OptIn` rather than suppressed.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealCoordinator.kt (1)

26-30: Add thread-confinement notes to public KDoc.

Public methods mutate shared coordinator state, but KDoc doesn’t state thread expectations (e.g., main-thread use) or state guarantees.

As per coding guidelines: **/*.kt: "Document public APIs with KDoc, including thread expectations and state notes".

Also applies to: 34-66

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealCoordinator.kt`
around lines 26 - 30, Update the KDoc for the public class
SwipeRevealCoordinator to include thread-confinement and state guarantees:
explicitly state that all public methods that mutate shared coordinator state
must be called from the main/UI thread (or specify the required dispatcher),
describe whether calls are reentrant or synchronized, and note any
atomicity/visibility guarantees clients can rely on; also add the same
thread/state note to each public method in the class so callers see the
requirement at the call site.
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt (1)

63-73: Consider safe defaults for destructive actions.

LeaveGroup and DeleteConversation are destructive, but confirmationPopup defaults to null (Line 67, Line 108). That makes accidental immediate execution easy when a caller forgets to pass confirmation metadata.

Also applies to: 104-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt`
around lines 63 - 73, LeaveGroup and DeleteConversation currently have
confirmationPopup defaulting to null making destructive actions prone to
accidental immediate execution; update their constructors (classes LeaveGroup
and DeleteConversation) so confirmationPopup defaults to a safe non-null
ConfirmationPopup (e.g., a standard destructive confirmation with title, message
and confirm/cancel labels) instead of null, and ensure callers can still
override it by providing a custom ConfirmationPopup via the confirmationPopup
parameter declared on the ChannelAction implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.planning/PROJECT.md:
- Line 5: The planning note "Migrating stream-chat-android's release process to
match stream-video-android and stream-core-android" (the line referencing "No
code changes") is out of scope for this channel-list redesign PR; either remove
or relocate that planning track to its own PR and update .planning/PROJECT.md so
its content matches the channel-list redesign intent, specifically edit or
remove the sentence at the paragraph containing "Migrating stream-chat-android's
release process..." (and the duplicated lines mentioning "No code changes"
around the second occurrence) so the document no longer references the
release-process migration in this PR.

In
`@stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt`:
- Around line 34-38: The test helper in UserRobotChannelListAsserts uses a
nullable Boolean parameter (fromCurrentUser: Boolean?) to encode three modes and
lacks KDoc; create a new enum (e.g., PreviewAuthorPrefix with values
CURRENT_USER, OTHER_USER, NONE), replace the fromCurrentUser: Boolean? parameter
with prefix: PreviewAuthorPrefix in the relevant function(s) that build
expectedPreview, update the logic that computes expectedPreview (the when block
that currently checks true/false/null) to switch on PreviewAuthorPrefix, add
KDoc to the public helper explaining the enum and each mode, and update all call
sites as suggested (true → CURRENT_USER, false → OTHER_USER, omit → NONE).

In
`@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt`:
- Around line 50-73: The animateItem() modifier is applied to ChannelItem (so
animation is ignored) but must be applied to the direct lazy child; move
animateItem() into the SwipeableChannelItem modifier parameter and keep only the
pinned background on ChannelItem. Specifically, pass Modifier.animateItem() (and
any other base modifiers such as backgroundCoreApp) into SwipeableChannelItem’s
modifier argument (SwipeableChannelItem(... modifier = Modifier.animateItem()
...)), and update ChannelItem to receive itemModifier that only sets the pinned
background when channelItem.channel.isPinned() without calling animateItem();
apply the same change in ChannelListItemContent to ensure animations work when
swipeEnabled is true.

In `@stream-chat-android-compose/api/stream-chat-android-compose.api`:
- Around line 4049-4059: The change to
MessagePreviewFormatter.formatMessagePreview added a new parameter
isDirectMessaging, which breaks external implementations; restore backward
compatibility by adding an overload with the previous signature
(formatMessagePreview(Message, User): AnnotatedString) that delegates to the new
signature (formatMessagePreview(Message, User, Boolean)) using the same default
boolean you intend (match the default used in formatMessagePreview$default), so
existing custom formatters continue to work; add this overload either as a
concrete default implementation in MessagePreviewFormatter (or in
MessagePreviewFormatter.DefaultImpls) and ensure Companion.defaultFormatter and
callers still resolve to the new three-arg method.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelList.kt`:
- Around line 178-191: The swipeActionHandler currently hard-codes calls to
viewModel methods for specific ChannelAction subclasses (MuteChannel,
UnmuteChannel, PinChannel, UnpinChannel, ArchiveChannel, UnarchiveChannel),
which bypasses any custom behavior implemented in ChannelAction.onAction; change
the handler to invoke action.onAction(context) (or the action's onAction API)
for all actions, delegating to the action itself, and only fall back to
viewModel.executeOrConfirm(action) if the action does not implement/onAction
returns a sentinel; keep the existing scope.launch { swipeCoordinator.closeAll()
} and ensure the handler still closes the swipe before delegating so custom
onAction logic runs while maintaining current cleanup.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelListLoadingItem.kt`:
- Around line 58-66: The code uses Modifier.weight(...) inside
ChannelListLoadingItem (and the Row/ShimmerProgressIndicator blocks) but is
missing the androidx.compose.foundation.layout.weight import; add an import for
androidx.compose.foundation.layout.weight to the top of the file so the
Modifier.weight calls compile, then rebuild to verify compilation.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeableChannelItem.kt`:
- Around line 74-76: The function SwipeableChannelItem uses the experimental
AnchoredDraggableState (constructed with initialValue = SwipeRevealValue.Closed)
without opt-in; add the explicit opt-in annotation
`@OptIn`(ExperimentalFoundationApi::class) to the SwipeableChannelItem declaration
so the use of AnchoredDraggableState is properly opted-in (follow the same
pattern used in FileAttachmentContent.kt/GiphyAttachmentContent.kt).

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionItem.kt`:
- Line 38: Update the KDoc for the label parameter on both SwipeActionItem
overloads: replace the inaccurate "The label text below the icon" description
with a statement that it is an accessibility label used as the Icon's
contentDescription (i.e., an accessibility/content description string), since
the label is not rendered as visible Text but passed to the Icon's
contentDescription. Reference the label parameter in the SwipeActionItem
functions and mention its use with the Icon composable (contentDescription) so
reviewers can verify the change.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealCoordinator.kt`:
- Around line 54-59: The code iterates the mutable registry while calling the
suspending state.animateTo(...) in onItemOpened (and the similar onItemClosed
block), which can be mutated by unregister() during suspension and cause
ConcurrentModificationException; fix this by iterating over a snapshot of the
registry (e.g., registry.toList() or registry.values.toList()) and then calling
animateTo on those snapshot entries so mutations to registry during suspension
don't affect the iterator; update the iteration in onItemOpened and the
corresponding onItemClosed (and any other places that call state.animateTo while
iterating registry) to use the snapshot before invoking animateTo.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt`:
- Around line 168-174: IsErrorIcon currently passes null for contentDescription
which breaks accessibility; update the IsErrorIcon composable to use a non-null
localized string (e.g.
R.string.stream_ui_message_list_semantics_message_status_error) as the
contentDescription and add the matching string entry to strings.xml to mirror
the pattern used by IsPendingIcon/IsSentIcon/IsDeliveredIcon so TalkBack/Switch
Access announce the error state.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.kt`:
- Around line 51-52: The magic numeric literal 50 used to create the pill shape
should be replaced with a named constant to remove the undocumented `@Suppress`
and improve readability: introduce a private constant (e.g., PILL_CORNER_PERCENT
= 50) near the top of UnreadCountIndicator.kt and use
RoundedCornerShape(PILL_CORNER_PERCENT) in place of the literal so the
`@Suppress`("MagicNumber") can be removed and the shape variable remains
RoundedCornerShape(…) for the pill styling.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt`:
- Around line 419-431: The animated placement modifier animateItem() is applied
only to the inner ChannelItem, causing the swipe background/actions in
SwipeableChannelItem to remain unsynchronized; move the Modifier.animateItem()
to the outer composable by applying it to SwipeableChannelItem (in the branch
where swipeEnabled is true) instead of inside ChannelItem so the whole swipe
container (including ChannelSwipeActions/background) participates in placement
animations while keeping ChannelItem’s existing modifiers and parameters
unchanged.

In `@stream-chat-android-compose/src/main/res/values/strings.xml`:
- Around line 323-324: The two string resources
stream_compose_poll_created_preview and stream_compose_poll_closed_preview are
marked translatable="false" but are user-facing; remove the translatable="false"
attribute (or set it to true) so these resources become localizable and ensure
corresponding translations are added to the appropriate
values-<locale>/strings.xml files; update any documentation/comments referencing
these resource names if necessary to indicate they require translation.

In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/channels/ChannelItemTest.kt`:
- Around line 57-70: The test muting snapshot uses snapshot { } instead of the
dark-mode variant; update the test function muting snapshot by replacing
snapshot { } with snapshotWithDarkMode { } (the test function `fun \`muted
channel trailing bottom\`()` that renders `ChannelItemMutedTrailingBottom()`
inside `ChatTheme` with `ChatConfig(channelList =
ChannelListConfig(muteIndicatorPosition =
MuteIndicatorPosition.TRAILING_BOTTOM))`) so the component is validated in both
light and dark themes.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt`:
- Around line 24-35: Update the KDoc for the ChannelAction
sealed/interface/class and its public properties (e.g., ChannelAction, onAction,
confirmationPopup, requiredCapability, isDestructive, icon, label) to include
explicit thread and state expectations: state whether instances are immutable
after creation, which thread or dispatcher onAction will be invoked on (UI/main
thread, IO, or caller thread), whether callers must marshal to a specific thread
to call onAction, and any lifecycle/ownership notes for confirmationPopup and
channel references; add similar KDoc sentences to each public concrete action
class/block so every public API in this file documents threading and state
guarantees clearly.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/actions/internal/ChannelActionsDialogFragment.kt`:
- Around line 165-168: channelOptionTextView.setStartDrawable currently
force-unwraps requireContext().getDrawableCompat(action.icon)!! which can NPE
for custom ChannelAction icons; change this to handle null safely by obtaining
the drawable from requireContext().getDrawableCompat(action.icon) and if null
use a safe fallback drawable or call requireNotNull(...) with a descriptive
message referencing the ChannelAction icon (e.g., include action.id or
action.label) before passing to setStartDrawable; update
ChannelActionsDialogFragment where channelOptionTextView and setStartDrawable
are used to avoid the !! and ensure a non-null Drawable is always provided.

---

Outside diff comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt`:
- Around line 74-92: Update the KDoc for the public MessageReadStatusIcon
composable to document the newly-handled FAILED_PERMANENTLY state: mention that
the composable now renders an error/failed state when message.syncStatus ==
SyncStatus.FAILED_PERMANENTLY (or equivalent), describe which icon/composable
(e.g., isPendingIcon/isSentIcon/isDeliveredIcon or a new failure representation)
will be shown in that case, and add an `@param` entry or sentence explaining when
and why the failed state is displayed so callers understand the full state
machine.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt`:
- Around line 97-101: The clear button and placeholder visibility are currently
driven by the external `query` prop which lags the local input buffer, causing
flicker; change the visibility checks to use the local compose state
(`textState`) instead—e.g., in the `trailingContent` assignment (where
`isFocused && query.isNotEmpty()` is used) and the placeholder visibility logic
(lines referenced around 163-164) use `isFocused && textState.text.isNotEmpty()`
or `textState.value.text.isNotEmpty()` depending on your TextFieldValue type so
the UI reacts immediately to local edits before the parent `query` updates.

---

Duplicate comments:
In @.planning/ROADMAP.md:
- Around line 1-130: Remove the stray "[duplicate_comment]" marker from the
review comment content associated with "Roadmap: Release Process Migration" so
the comment reads only the intended sentence ("No additional issues in this file
beyond the scope concern already noted.")—locate the review text containing the
duplicate token and delete it, leaving the comment clean and unambiguous.

---

Nitpick comments:
In @.planning/PROJECT.md:
- Around line 44-49: Update the Constraints section to explicitly state that PRs
must target the develop branch and add a coordination note that any changes to
versioning, publishing, changelog or release-process artifacts (e.g.,
CHANGELOG.md, release scripts) require prior sync with the release owner;
reference the "Constraints" heading and the file name CHANGELOG.md so reviewers
can find and enforce the rule.

In @.planning/STATE.md:
- Around line 5-9: Update the time-sensitive block by adding explicit ownership
and a refresh cadence so status remains fresh: add fields such as "Owner:
<team/person>" and "Refresh cadence: <frequency, e.g.
weekly/monthly/after-change>" alongside the existing "Phase", "Plan", "Status",
and "Last activity" entries, and include a short sentence describing who is
responsible for updates and when to update (e.g., "Owner will review and refresh
this status every X days or after significant changes").

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.kt`:
- Line 214: The suppression of LongMethod should be removed and
DefaultChannelItemCenterContent refactored into smaller helper composables:
extract the title row rendering into a function like renderChannelTitleRow, the
preview/last-message row into renderChannelPreviewRow, and status/mute and
unread badge logic into renderChannelStatusOrMute; update
DefaultChannelItemCenterContent to compose these helpers, keep parameter lists
minimal and pass only necessary state (channel, lastMessagePreview, isMuted,
isOnline, unreadCount), and add explicit `@OptIn` annotations if any experimental
APIs are used instead of suppressing LongMethod.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/DefaultChannelSwipeActions.kt`:
- Around line 164-170: The current selection logic in DefaultChannelSwipeActions
builds candidates = listOf(...) containing archiveAction, muteAction, pinAction
and then returns candidates.firstOrNull { it != null }, but since pinAction (and
archiveAction/pinAction) are non-null the function can never return null; update
the implementation to reflect that non-null guarantee: either change the
function return type to non-nullable and return
candidates.filterNotNull().first() (or .first { true } after filtering) or, if
null is genuinely possible, adjust the candidates construction so some entries
can be null and use filterNotNull().firstOrNull(); reference the candidates
variable and the archiveAction/muteAction/pinAction symbols when making the
change.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt`:
- Line 93: The horizontalArrangement uses
Arrangement.spacedBy(StreamTokens.spacingMd) which adds 16dp between all
children while the center content also has start = StreamTokens.spacing2xs
(4dp), producing a 20dp gap; to match the Figma 16dp edge-to-edge spacing remove
the center content's start padding (the padding on the composable that sets
start = StreamTokens.spacing2xs) or alternatively remove the
Arrangement.spacedBy and apply per-child paddings so only one 16dp gap exists
between the avatar and text; update the code references:
horizontalArrangement/Arrangement.spacedBy(StreamTokens.spacingMd) and the
center content padding (start = StreamTokens.spacing2xs) accordingly.
- Around line 207-213: The explicit textStyle override on the Timestamp
composable is redundant because Timestamp already defaults to
ChatTheme.typography.captionDefault.copy(color = ChatTheme.colors.textTertiary,
lineHeight = StreamTokens.lineHeightNormal); remove the textStyle parameter from
the Timestamp call (the one using searchResultItemState.message.createdAt) so it
relies on the default style provided by Timestamp/ChatTheme.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeActionStyle.kt`:
- Around line 21-36: The public enum SwipeActionStyle lacks KDoc about
thread/state expectations; update the KDoc for SwipeActionStyle to add a short
thread/state note stating that this enum is a stateless, immutable style marker
and safe to reference from any thread (no synchronization required), and include
that it carries no mutable state and is safe for concurrent use in UI code such
as composables and background threads.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SwipeRevealCoordinator.kt`:
- Around line 26-30: Update the KDoc for the public class SwipeRevealCoordinator
to include thread-confinement and state guarantees: explicitly state that all
public methods that mutate shared coordinator state must be called from the
main/UI thread (or specify the required dispatcher), describe whether calls are
reentrant or synchronized, and note any atomicity/visibility guarantees clients
can rely on; also add the same thread/state note to each public method in the
class so callers see the requirement at the call site.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt`:
- Line 95: The suppression on buildDefaultChannelActions is too broad and
undocumented; remove the blanket `@Suppress`("LongMethod", "LongParameterList")
and either refactor buildDefaultChannelActions into smaller helper functions
(e.g., extract action creation into createChannelActionList, buildActionItem,
configureActionHandlers) to reduce method length and parameter count, or if
suppression is unavoidable, replace it with a narrow, documented suppression
scoped to the specific offending expression(s) and add a one-line rationale
comment above it; also ensure any experimental APIs used are annotated with
`@OptIn` rather than suppressed.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessageReadStatusIcon.kt`:
- Around line 83-110: The MessageReadStatusIcon API hard-codes IsErrorIcon for
SyncStatus.FAILED_PERMANENTLY and should expose an error slot for parity; add a
new parameter isErrorIcon: `@Composable` () -> Unit to MessageReadStatusIcon
(defaulting to { IsErrorIcon(modifier = modifier) }) and call that isErrorIcon()
in the FAILED_PERMANENTLY branch instead of directly invoking IsErrorIcon, so
integrators can override the error state rendering.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.kt`:
- Around line 36-47: Update the KDoc for the composable function
UnreadCountIndicator to include the missing `@param` entry for color, describing
that it controls the badge tint and noting the default value
(ChatTheme.colors.accentPrimary) and its intent (to match the app's accent for
unread badges); keep other parameter docs intact and ensure the KDoc reflects
the changed default from the previous accentError to accentPrimary so callers
understand the behavioral change.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/SearchInput.kt`:
- Line 74: Remove the undocumented `@Suppress`("LongMethod") on SearchInput:
instead split the large function by extracting the state synchronization logic
and the TextField decoration into small helpers (e.g., create functions named
syncSearchState(...) and decorateSearchTextField(...) called from SearchInput)
so the body is shorter and the lint rule is satisfied; if any experimental APIs
are required, replace suppressions with explicit `@OptIn`(...) annotations on
SearchInput or the new helper functions and document the reason in a brief KDoc.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewFormatter.kt`:
- Around line 63-67: Add KDoc for the public function formatMessagePreview and
its new parameter isDirectMessaging: document what the function returns,
expected thread/state assumptions, and explain the boolean’s effect (e.g., how
message preview differs in direct messaging vs channels). Remove the
undocumented `@Suppress`("LongMethod") (or if you must keep it, add a KDoc
justification comment explaining why the method legitimately exceeds length and
why splitting isn't appropriate). Ensure the KDoc appears above the
formatMessagePreview declaration and explicitly describes isDirectMessaging
behavior and any thread/state constraints.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory.kt`:
- Around line 67-132: Remove the undocumented `@Suppress`("LongMethod") on
MessagePreviewIconFactory.createPreviewIcons and refactor duplicated
InlineTextContent + Icon creation into a small private helper (e.g.,
buildPreviewIcon(drawableRes: Int): InlineTextContent) that captures the
Placeholder and Icon modifier; then replace each anonymous InlineTextContent
block (for VOICE_MESSAGE, PHOTO, VIDEO, FILE, LINK, LOCATION, POLL) with calls
to that helper, keeping Placeholder and iconModifier shared and eliminating the
long-method suppression.

In
`@stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_search.xml`:
- Around line 24-29: Replace the hardcoded semantic gray in the search drawable
with the project's placeholder color: open stream_compose_ic_search.xml and
change the android:strokeColor attribute (and any other explicit semantic colors
in this drawable) from "#687385" to the neutral placeholder (e.g. "#FFFFFF") so
the Compose layer can apply tint programmatically; ensure android:fillColor
remains transparent and strokeWidth/strokeLineCap/strokeLineJoin are unchanged.

In `@stream-chat-android-compose/src/main/res/values/strings.xml`:
- Around line 31-34: The plural resource stream_compose_channel_list_typing_many
uses unpositioned numeric placeholders ("%d"); update both <item> entries to use
positional placeholders ("%1$d") so translators can reorder safely and maintain
consistent formatting; search for other plural resources (e.g., the similar
block referenced at lines 182-193) and apply the same replacement to their
numeric placeholders as well to ensure all plural strings use "%1$d".

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt`:
- Around line 63-73: LeaveGroup and DeleteConversation currently have
confirmationPopup defaulting to null making destructive actions prone to
accidental immediate execution; update their constructors (classes LeaveGroup
and DeleteConversation) so confirmationPopup defaults to a safe non-null
ConfirmationPopup (e.g., a standard destructive confirmation with title, message
and confirm/cancel labels) instead of null, and ensure callers can still
override it by providing a custom ConfirmationPopup via the confirmationPopup
parameter declared on the ChannelAction implementations.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ConfirmationPopup.kt`:
- Around line 19-30: Update the KDoc for the public data class ConfirmationPopup
to include thread and state expectations: state that this is an immutable value
object (data class) safe to pass across threads, its properties (title, message,
confirmButtonText) are resolved UI text and should not hold mutable or
lifecycle-bound references, and note any expectations about resolution timing
(e.g., already localized/resolved before construction) so callers know it's safe
for background/worker threads and UI usage; reference the ConfirmationPopup type
and its properties when adding these notes.

- Fix deleted messages filtered out in channel preview (missing isDeleted predicate)
- Fix hardcoded icon color breaking XML SDK dark mode (tint with text style color)
- Fix ConcurrentModificationException in SwipeRevealCoordinator (snapshot registry)
- Add contentDescription to IsErrorIcon for accessibility
- Remove isPinned from ChannelItemState (derive from Channel.isPinned extension)
- Move animateItem from inner ChannelItem to outer SwipeableChannelItem
- Simplify header trailing content nesting and use AvatarSize constants
- Clip avatar clickable area to CircleShape for proper ripple shape
- Add text overflow for members status in SelectedChannelMenu
- Remove migration artifact comments from SearchResultItem
- Fix SwipeActionItem label KDoc (contentDescription, not visible text)
- Add @OptIn(ExperimentalFoundationApi) to SwipeableChannelItem
- Remove .planning directory from git tracking
…eedback

- Separate onStartChatClick from onHeaderActionClick in ChannelsScreen (P4+P5)
- Rename onChannelOptionClick → onChannelOptionConfirm for semantic clarity (P3+P15)
- Remove deprecated 2-param formatMessagePreview overload (v7 breaking change)
- Move animateItem() to outer SwipeableChannelItem in sample CustomChatComponentFactory
- Add dark mode snapshot for muted channel trailing bottom test
- Make poll preview strings translatable
- Remove leftover comment and unnecessary Box wrapper in header
- Use applyIf extension for focus/selected modifiers in ChannelItem
- Use AvatarSize.ExtraLarge constant instead of raw 48.dp
- Extract TitleRow and MessageRow composables from center content
- Remove verbose section-divider comments
- Delete accidentally committed GAPS.md
- Replace raw dp values with StreamTokens in empty state
- Replace OutlinedButton with StreamTextButton secondaryOutline
- Remove RowScope extension from DefaultChannelSwipeActions
- Wrap swipe action resolution in remember with resources
- Make swipe actions self-executing via onAction + rememberUpdatedState
- Replace hardcoded Color.White with theme tokens in SwipeActionItem
- Replace RoundedCornerShape(50) with CircleShape in UnreadCountIndicator
- Update public API dump
Loading state changed from CircularProgressIndicator to skeleton shimmer.
Update ChannelsScreenTest and ChatsScreenTest to assert on testTag instead
of ProgressBarRangeInfo. Re-record all affected snapshots.
@aleksandar-apostolov aleksandar-apostolov removed the pr:new-feature New feature label Feb 26, 2026
Reduce parameter count in TitleRow/MessageRow by passing channelItemState
directly. Extract swipe action builders to shorten rememberPrimarySwipeAction.
Wrap long KDoc lines in ChatComponentFactory.
Channel preview now shows 'Message deleted' instead of falling back
to the previous message, matching the redesigned behavior.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
41.8% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:breaking-change Breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants